﻿using System;
using System.Collections.Generic;
using System.Data;
using Jayden.Dll.Core.Persistence.Attributes;
using Jayden.Dll.Core.Persistence.Models;
using Jayden.Dll.Core.Persistence.Queries;

namespace Jayden.Dll.Core.Persistence.Tools
{
	/// <summary>This is an SQLite implementation (todo: interface and others dbms)</summary>
	public class SQLiteHelper : SQLHelper
	{
		public new static readonly SQLiteHelper Instance = new SQLiteHelper();

		public new static DbType GetDefaultDbType(Type type)
		{
			if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
				return GetDefaultDbType(Nullable.GetUnderlyingType(type));
			if (type == typeof(DateTime))
				return DbType.Int64;
			return SQLHelper.GetDefaultDbType(type);
		}

		public override string GenerateField(Field field, Index pk)
		{
			string name = field.FieldName;
			string type = GenerateType(field.DbType);
			List<string> constraints = new List<string>();
			if (field.PrimaryKey && pk.Length == 1)
			{
				constraints.Add("PRIMARY KEY");
				if(field.AutoIncrement)
					constraints.Add("AUTOINCREMENT");
			}
			if (field.Unique)
				constraints.Add("UNIQUE");
			if (field.DefaultValue != null)
				constraints.Add(string.Format("DEFAULT {0}", GetFormattedDefaultValue(field)));
			if(!field.Nullable)
				constraints.Add("NOT NULL");
			string fieldDef = string.Format("{0} {1} {2}", name, type, string.Join(" ", constraints));
			return fieldDef.Trim();
		}

		public override string GetFormattedDefaultValue(Field field)
		{
			if (field.DefaultValue is DateTime)
			{
				DateTime dt = (DateTime)field.DefaultValue;
				long fullDateTime =
					dt.Second +
					100 * dt.Minute +
					10000 * dt.Hour +
					1000000 * dt.Day +
					100000000 * dt.Month +
					10000000000 * dt.Year;
				field.DefaultValue = fullDateTime;
				return base.GetFormattedDefaultValue(field);
			}
			return base.GetFormattedDefaultValue(field);
		}

		public override string GeneratePrimaryKey(Index pk)
		{
			if (pk.Length == 1)
				return null;
			return base.GeneratePrimaryKey(pk);
		}

		public override string GenerateType(DbType type)
		{
			switch (type)
			{
				case DbType.AnsiString:
				case DbType.AnsiStringFixedLength:
				case DbType.String:
				case DbType.StringFixedLength:
				case DbType.Time:
					return "TEXT";
				case DbType.Single:
				case DbType.Double:
					return "REAL";
				case DbType.Decimal:
					return "NUMERIC";
				case DbType.Boolean:
				case DbType.Int16:
				case DbType.Int32:
				case DbType.Int64:
				case DbType.Byte:
				case DbType.SByte:
				case DbType.UInt16:
				case DbType.UInt32:
				case DbType.UInt64:
				case DbType.Date:
				case DbType.DateTime:
					return "INTEGER";
				case DbType.Binary:
					return "NONE";
				default:
					throw new NotImplementedException(string.Format("DbType '{0}' cannot be generated for this DBMS.", type));
			}
		}

		public override string GenerateSelect(string[] select, string[] from, string where, string[] groupBy, string[] orderBy, int? limit, int? offset)
		{
			List<string> parts = new List<string>();
			parts.Add("SELECT");
			if (select == null || select.Length <= 0)
				parts.Add("*");
			else
				parts.Add(string.Join(",", select));
			parts.Add("FROM");
			parts.Add(string.Join(",", from));
			if (where != null && !string.IsNullOrWhiteSpace(where))
			{
				parts.Add("WHERE");
				parts.Add(where);
			}
			if (groupBy != null && groupBy.Length > 0)
			{
				parts.Add("GROUP BY");
				parts.Add(string.Join(",", groupBy));
			}
			if (orderBy != null && orderBy.Length > 0)
			{
				parts.Add("ORDER BY");
				parts.Add(string.Join(",", orderBy));
			}
			if (limit.HasValue)
			{
				parts.Add("LIMIT");
				parts.Add(limit.Value.ToString());
			}
			if (offset.HasValue)
			{
				parts.Add("OFFSET");
				parts.Add(offset.Value.ToString());
			}
			return string.Join(" ", parts);
		}

		public override string GenerateQuery(Query query, int? limit, int? offset)
		{
			List<string> parts = new List<string>();
			parts.Add(query.ToString());
			if (limit.HasValue)
			{
				parts.Add("LIMIT");
				parts.Add(limit.Value.ToString());
			}
			if (offset.HasValue)
			{
				parts.Add("OFFSET");
				parts.Add(offset.Value.ToString());
			}
			return string.Join(" ", parts);
		}
	}
}
